// $codepro.audit.disable
package org.mysofts.eclipseman; // $codepro.audit.disable tooManyViolations

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.mysofts.eclipseman.archives.Archive;
import org.mysofts.eclipseman.archives.ArchiveFactory;
import org.mysofts.eclipseman.exceptions.InstanciateArchiveException;
import org.mysofts.eclipseman.exceptions.UnsupportedFileTypeException;
import org.mysofts.eclipseman.util.AppConfig;
import org.mysofts.eclipseman.util.AppLog;
import org.mysofts.eclipseman.util.MessageProvider;

/**
 * Manager qui liste les installations d'eclipse dans le repertoire
 * {@code #ECLIPSE_DIRECTORY}.
 * 
 * @author reda
 * 
 */
public final class EclipseInstallations {

	// [start] static fields
	/**
	 * Nom par defaut du répertoire d'extraction d'une archive eclipse.
	 */
	private static final String DEFAULT_DIRECTORY_NAME = "eclipse";
	/**
	 * Repertoire d'installation des eclipse.
	 */
	public static final File ECLIPSE_DIRECTORY = new File(
			AppConfig.ECLIPSES_PATH.getValue());
	/**
	 * Répertoire des archives.
	 */
	public static final File ARCHIVE_DIRECTORY = new File(
			AppConfig.ARCHIVE_STORE_PATH.getValue());
	// [end]

	// [start] singleton
	/**
	 * Instance unique de cette classe.
	 */
	private static EclipseInstallations instance = null;

	/**
	 * Retourne l'instance unique de cette classe.
	 * 
	 * @return Instance unique de cette classe.
	 */
	public static EclipseInstallations get() {
		if (null == instance) {

			if (!ECLIPSE_DIRECTORY.exists()) {
				ECLIPSE_DIRECTORY.mkdirs();
			}
			if (!ARCHIVE_DIRECTORY.exists()) {
				ARCHIVE_DIRECTORY.mkdirs();
			}
			instance = new EclipseInstallations();
		}

		return instance;
	}

	/**
	 * Constructeur interne de cette classe.
	 */
	private EclipseInstallations() {
		this.refresh();
	}

	// [end]

	// [start] fields
	/**
	 * Map d'eclipses.
	 */
	private final Map<String, Eclipse> eclipses = new HashMap<String, Eclipse>();
	/**
	 * Liste des archives d'eclipse.
	 */
	private final List<Archive> archives = new ArrayList<Archive>();

	// [end]

	// [start] méthodes privées
	/**
	 * Recharge la liste des archive depuis le store des archive.
	 */
	private void loadStoredArchives() {
		if (ARCHIVE_DIRECTORY.exists() && ARCHIVE_DIRECTORY.isDirectory()) {
			File[] files = ARCHIVE_DIRECTORY
					.listFiles((FilenameFilter) new SuffixFileFilter(
							ArchiveFactory.getArchiveExtensions(),
							IOCase.INSENSITIVE));
			for (File file : files) {
				Exception loggedException = null;
				try {
					Archive ar = ArchiveFactory.createArchiveManager(file);
					AppLog.debug(MessageFormat.format(
							MessageProvider.get("logs")
									.sub("eclipse-installations")
									.sub("load-archive").sub("recognized")
									.value(), file.getName(), ar.getClass()
									.getSimpleName()));
					this.archives.add(ar);
				} catch (UnsupportedFileTypeException e) {
					loggedException = e;
				} catch (InstanciateArchiveException e) {
					loggedException = e;
				}

				if (loggedException != null) {
					AppLog.warn(MessageFormat.format(
							MessageProvider.get("logs")
									.sub("eclipse-installations")
									.sub("load-archive")
									.sub("unsupported-type").value(),
							file.getName()), loggedException);
				}
			}
		}
	}

	/**
	 * Recharge la liste des eclipses installés.
	 */
	private void loadEclipses() {
		AppLog.debug(MessageFormat.format(
				MessageProvider.get("logs").sub("eclipse-installations")
						.sub("load-eclipse").sub("start").value(),
				ECLIPSE_DIRECTORY.getAbsolutePath()));

		if (ECLIPSE_DIRECTORY.exists() && ECLIPSE_DIRECTORY.isDirectory()) {
			File[] dirs = ECLIPSE_DIRECTORY.listFiles(new FileFilter() {
				@Override
				public boolean accept(final File dir) {
					return Eclipse.isValidEclipseInstallation(dir);
				}
			});
			for (File dir : dirs) {
				AppLog.debug(MessageFormat.format(
						MessageProvider.get("logs")
								.sub("eclipse-installations")
								.sub("load-eclipse").sub("recognized").value(),
						dir.getName()));
				Eclipse eclipse = new Eclipse(dir);
				this.eclipses.put(eclipse.getName(), eclipse);
			}
		}
		AppLog.debug(MessageFormat.format(
				MessageProvider.get("logs").sub("eclipse-installations")
						.sub("load-eclipses").sub("end").value(),
				ECLIPSE_DIRECTORY.getAbsolutePath()));
	}

	// [end]

	// [start] méthode publiques de gestion communes
	/**
	 * Recharge la liste des eclipses reconnus.
	 */
	public void refresh() {
		this.eclipses.clear();
		loadEclipses();

		this.archives.clear();
		loadStoredArchives();
	}

	// [end]

	// [start] méthodes publiques pour gestion des eclipses
	/**
	 * Retourne l'installation d'eclipse avec un nom.
	 * 
	 * @param name
	 *            Nom de l'installation d'eclipse.
	 * @return Installation d'eclipse qui a ce nom, ou {@code null} si aucun
	 *         eclipse n'a ce nom.
	 */
	public Eclipse getEclipse(final String name) {
		return this.eclipses.get(name);
	}

	/**
	 * Retourne un tableau des eclipses installés.
	 * 
	 * @return Tableau des eclipses installés.
	 */
	public Eclipse[] getInstalledEclipses() {
		return this.eclipses.values().toArray(new Eclipse[0]);
	}

	// [end]

	// [start] méthode publiques de gestion des archives dans le store
	/**
	 * Retourne un tableau des archive d'eclipse dans le cache.
	 * 
	 * @return Tableau des archives.
	 */
	public Archive[] getStoredArchives() {
		return this.archives.toArray(new Archive[0]);
	}

	/**
	 * Charge une instance {@link Archive} à partir d'un fichier, si ce fichier
	 * est similaire à une archive dans le store l'instance dans le store sera
	 * retournée.
	 * 
	 * @param archiveFile
	 *            Fichier archive à comparer.
	 * @return Archive en store qui possède le même nom que le fichier passé en
	 *         paramètre, ou nouvelle instance si l'archive est reconnue.
	 * @throws FileNotFoundException
	 *             Si le fichier n'a pas été trouvé.
	 * @throws UnsupportedFileTypeException
	 *             Si le type de l'archive n'est pas reconnue.
	 * @throws InstanciateArchiveException
	 *             Erreur lors de l'instanciation de l'objet {@link Archive}.
	 */
	public Archive loadArchive(final File archiveFile)
			throws FileNotFoundException, UnsupportedFileTypeException,
			InstanciateArchiveException {
		if (archiveFile.isFile()) {
			Archive ar = ArchiveFactory.createArchiveManager(archiveFile);

			if (null != ar) {
				for (Archive ar2 : this.archives) {
					if (ar2.similarTo(ar)) {
						return ar2;
					}
				}
			} else {
				throw new UnsupportedFileTypeException(archiveFile);
			}

			return ar;
		} else {
			throw new FileNotFoundException(MessageFormat.format(
					MessageProvider.get("logs").sub("eclipse-installations")
							.sub("load-archive").sub("file-not-found").value(),
					archiveFile.getAbsolutePath()));
		}
	}

	/**
	 * Copie une archive vers le cache des archives.
	 * 
	 * @param archive
	 *            Fichier d'archive à copier.
	 * @return Chemin vers la copie effectuée.
	 * @throws IOException
	 *             Si la copie a échouée.
	 */
	public Archive storeArchiveFile(final Archive archive) throws IOException {
		AppLog.debug(MessageFormat.format(
				MessageProvider.get("logs").sub("eclipse-installations")
						.sub("store-archive").sub("start").value(), archive
						.getArchiveFile().getAbsolutePath()));
		try {
			if (!this.archives.contains(archive)) {
				AppLog.debug(MessageFormat
						.format(MessageProvider.get("logs")
								.sub("eclipse-installations")
								.sub("store-archive").sub("notstorred").value(),
								archive.getArchiveFile().getAbsolutePath()));
				File newfile = new File(ARCHIVE_DIRECTORY, archive
						.getArchiveFile().getName());

				Archive newArchive = archive.copyToOtherLocation(newfile);
				AppLog.debug(MessageFormat.format(
						MessageProvider.get("logs")
								.sub("eclipse-installations")
								.sub("store-archive").sub("copied").value(),
						archive.getArchiveFile().getAbsolutePath(), newArchive
								.getArchiveFile().getAbsolutePath()));

				this.archives.add(newArchive);

				return newArchive;
			} else {
				AppLog.debug(MessageFormat.format(
						MessageProvider.get("logs")
								.sub("eclipse-installations")
								.sub("store-archive").sub("already-exists")
								.value(), archive.getArchiveFile()
								.getAbsolutePath()));
				return archive;
			}
		} catch (IOException ex) {
			AppLog.error(MessageFormat.format(
					MessageProvider.get("logs").sub("eclipse-installations")
							.sub("store-archive").sub("error").value(), archive
							.getArchiveFile().getAbsolutePath()), ex);
			throw ex;
		} finally {
			AppLog.info(MessageFormat.format(
					MessageProvider.get("logs").sub("eclipse-installations")
							.sub("store-archive").sub("end").value(), archive
							.getArchiveFile().getAbsolutePath()));
		}
	}

	/**
	 * Installe une archive d'eclipse.
	 * 
	 * @param archiveFile
	 *            Fichier archive à extraire et à installer.
	 * @param newname
	 *            Nom de la nouvelle installation.
	 * @throws IOException
	 *             Si l'installation a généré une erreur.
	 */
	public void installArchive(final Archive archiveFile, final String newname)
			throws IOException {
		AppLog.debug(MessageFormat.format(
				MessageProvider.get("logs").sub("eclipse-installations")
						.sub("install-archive").sub("start").value(),
				archiveFile.getArchiveFile().getAbsolutePath(), newname));
		try {
			File newEclipseDirectory = new File(ECLIPSE_DIRECTORY, newname);
			File tempEclipseDirectory = new File(ECLIPSE_DIRECTORY,
					DEFAULT_DIRECTORY_NAME);

			if (newEclipseDirectory.exists()) {
				throw new IOException(MessageFormat.format(
						MessageProvider.get("logs")
								.sub("eclipse-installations")
								.sub("install-archive").sub("name-duplication")
								.value(), newname));
			}

			AppLog.debug(MessageProvider.get("logs")
					.sub("eclipse-installations").sub("install-archive")
					.sub("archive-verification").value());
			Collection<String> list = archiveFile.listFiles();
			if (!list.contains(DEFAULT_DIRECTORY_NAME + "/")) {
				AppLog.error(MessageProvider.get("logs")
						.sub("eclipse-installations").sub("install-archive")
						.sub("archive-verification").sub("invalid").value());
				throw new UnsupportedFileTypeException(MessageProvider
						.get("logs").sub("eclipse-installations")
						.sub("install-archive").sub("archive-verification")
						.sub("unsupported").value(),
						archiveFile.getArchiveFile());
			}

			AppLog.debug(MessageProvider.get("logs")
					.sub("eclipse-installations").sub("install-archive")
					.sub("extraction").value());
			archiveFile.extract(ECLIPSE_DIRECTORY);

			AppLog.debug(MessageProvider.get("logs")
					.sub("eclipse-installations").sub("install-archive")
					.sub("moving").value());
			FileUtils.moveDirectory(tempEclipseDirectory, newEclipseDirectory);

			AppLog.debug(MessageFormat.format(
					MessageProvider.get("logs").sub("eclipse-installations")
							.sub("install-archive").sub("end").value(),
					archiveFile.getArchiveFile(), newname, newEclipseDirectory));
		} catch (IOException ex) {
			AppLog.error(MessageFormat.format(
					MessageProvider.get("logs").sub("eclipse-installations")
							.sub("install-archive").sub("error").value(),
					archiveFile.getArchiveFile().getAbsolutePath(), newname),
					ex);
			throw ex;
		}
	}
	// [end]
}
